import { DocumentRegistry } from '@jupyterlab/docregistry';

import { ISharedDocument } from '@jupyter/ydoc'

import { ISignal, Signal } from '@lumino/signaling';

import { IChangedArgs } from '@jupyterlab/coreutils';

import { PartialJSONValue } from '@lumino/coreutils';

import { MindElixirDoc, MindElixirDocChange } from './mindElixirDoc';

export class MindElixirDocModel implements DocumentRegistry.IModel {
    private _dirty = false;
    private _isDisposed = false;
    private _readOnly = false;
    private _clientChanged = new Signal<this, Map<number, any>>(this);
    private _contentChanged = new Signal<this, void>(this);
    private _collaborationEnabled: boolean;
    private _stateChanged = new Signal<this, IChangedArgs<any>>(this);

    private _sharedModel: MindElixirDoc = MindElixirDoc.create();

    constructor(options: DocumentRegistry.IModelOptions<MindElixirDoc>) {
        const { collaborationEnabled, sharedModel } = options;
        this._collaborationEnabled = !!collaborationEnabled;
        console.log("this._collaborationEnabled: ", this._collaborationEnabled)
        if (sharedModel) {
            console.log("!!!!sharedModel is not null")
            this._sharedModel = sharedModel;
        } else {
            console.log("!!!!sharedModel is null")
            this._sharedModel = MindElixirDoc.create();
        }

        this._sharedModel.changed.connect(this._whenSharedModelChanged);
        this._sharedModel.awareness.on('change', this._whenClientChanged);
    }

    dispose(): void {
        if (this._isDisposed) {
            return
        }

        this._isDisposed = true;
        Signal.clearData(this);
    }

    get isDisposed(): boolean {
        return this._isDisposed;
    }

    /**
     * A signal emitted when the document content changes.
     */
    get contentChanged(): ISignal<this, void> {
        return this._contentChanged;
    }
    /**
     * A signal emitted when the model state changes.
     */
    get stateChanged(): ISignal<this, IChangedArgs<any>> {
        return this._stateChanged;
    }
    /**
     * get the signal clientChanged to listen for changes on the clients sharing
     * the same document.
     *
     * @returns The signal
     */
    get clientChanged(): ISignal<this, Map<number, any>> {
        return this._clientChanged;
    }
    /**
     * The dirty state of the model.
     *
     * #### Notes
     * This should be cleared when the document is loaded from
     * or saved to disk.
     */
    get dirty(): boolean {
        return this._dirty;
    }
    set dirty(newValue: boolean) {
        if (this._dirty === newValue) {
            return;
        }
        const oldValue = this._dirty;
        this._dirty = newValue;
        this._triggerStateChange({
            name: 'dirty',
            oldValue,
            newValue
        });
    }
    /**
     * The read-only state of the model.
     */
    get readOnly(): boolean {
        return this._readOnly;
    }
    set readonly(newValue: boolean) {
        if (this._readOnly === newValue) {
            return;
        }
        const oldValue = this._readOnly
        this._readOnly = newValue;
        this._triggerStateChange({ 
            name: 'readonly', 
            oldValue, 
            newValue 
        });
    }
    
    /**
     * The default kernel name of the document.
     */
    get defaultKernelName(): string {
        return ''
    }
    /**
     * The default kernel language of the document.
     */
    get defaultKernelLanguage(): string {
        return ''
    }
    /**
     * The shared notebook model.
     */
    get sharedModel(): ISharedDocument {
        if (this._sharedModel == null) {
            this._sharedModel = MindElixirDoc.create();
        }
        return this._sharedModel;
    }
    /**
     * Whether this document model supports collaboration when the collaborative
     * flag is enabled globally. Defaults to `false`.
     */
    get collaborative(): boolean {
        return this._collaborationEnabled;
    }
    /**
     * Serialize the model to a string.
     */
    toString(): string {
        console.log("toString called")
        if (!this._sharedModel) {
            return '';
        }
        return this._sharedModel.getSource();
    }
    /**
     * Deserialize the model from a string.
     *
     * #### Notes
     * Should emit a [contentChanged] signal.
     */
    fromString(value: string): void {
        console.log("fromString called")
        if (!this._sharedModel) {
            console.error("this._sharedModel is null")
            return;
        }
        this._sharedModel.setSource(value);
        this._triggerContentChange();
    }
    /**
     * Serialize the model to JSON.
     */
    toJSON(): PartialJSONValue {
        return JSON.parse(this.toString() || 'null');
    }
    /**
     * Deserialize the model from JSON.
     *
     * #### Notes
     * Should emit a [contentChanged] signal.
     */
    fromJSON(value: PartialJSONValue): void {
        this.fromString(JSON.stringify(value));
    }

    /**
     * Trigger a state change signal.
     */
    protected _triggerStateChange(args: IChangedArgs<any>): void {
        this._stateChanged.emit(args);
    }

    /**
     * Trigger a content changed signal.
     */
    protected _triggerContentChange(): void {
        this._contentChanged.emit(void 0);
        this.dirty = true;
    }

    /**
     * Callback to listen for changes on the sharedModel. This callback listens
     * to changes on the different clients sharing the document and propagates
     * them to the DocumentWidget.
     */
    private _whenClientChanged = () => {
        const clients = this.sharedModel.awareness.getStates();
        this._clientChanged.emit(clients);
    };

    /**
     * Callback to listen for changes on the sharedModel. This callback listens
     * to changes on shared model's content and propagates them to the DocumentWidget.
     *
     * @param sender The sharedModel that triggers the changes.
     * @param changes The changes on the sharedModel.
     */
    private _whenSharedModelChanged = (
        sender: MindElixirDoc,
        changes: MindElixirDocChange
    ): void => {
    };
}